home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / bin / pycentral < prev    next >
Encoding:
Text File  |  2010-09-14  |  93.6 KB  |  2,335 lines

  1. #! /usr/bin/python
  2.  
  3. import glob, logging, os, re, string, sys, time, cStringIO
  4. from optparse import OptionParser
  5. from ConfigParser import SafeConfigParser
  6.  
  7. sys.path[0:0] = ['/usr/share/pycentral-data', '/usr/share/python']
  8. import pyversions
  9.  
  10. try:
  11.     SetType = set
  12. except NameError:
  13.     import sets
  14.     SetType = sets.Set
  15.     set = sets.Set
  16.  
  17. program = os.path.basename(sys.argv[0])
  18.  
  19. shared_base = '/usr/share/pycentral/'
  20. shared_base2 = '/usr/share/pyshared/'
  21. pycentral_version = '0.6.16'
  22. req_pycentral_version = '0.6.15'
  23.  
  24.  
  25. def get_file_overwrite_error(existing_files):
  26.     """
  27.     helper that returns a error string to be passed to the user when
  28.     local files are detected (and querries if they are local installs
  29.     or part of another package)
  30.     """
  31.     from subprocess import PIPE, Popen
  32.     l = []
  33.     # ask dpkg what it knows about the files in question
  34.     (stdout, stderr) = Popen(["dpkg","-S"]+existing_files,
  35.                              env={"LANG" : "C"},
  36.                              stdout=PIPE, stderr=PIPE).communicate()
  37.     # this should never happen, dpkg should always give us
  38.     # something but we deal with it for robustness
  39.     if not stdout and not stderr:
  40.         l.append("Error, can not overwrite existing files:")
  41.         l.extend(existing_files)
  42.     # stdout has files that belong to packages
  43.     if stdout:
  44.         l.append("Not overwriting files owned by other packages:")
  45.         for line in map(string.strip, stdout.split("\n")):
  46.              if line:
  47.                  l.append("  "+line)
  48.     # stderr has local (or maintainer script created) files
  49.     if stderr:
  50.         l.append("Not overwriting local files:")
  51.         for line in map(string.strip, stderr.split("\n")):
  52.              if line:
  53.                  l.append("   "+line)
  54.     return "\n".join(l)
  55.  
  56. def samefs(path1, path2):
  57.     if not (os.path.exists(path1) and os.path.exists(path2)):
  58.         return False
  59.     while path1 != os.path.dirname(path1):
  60.         if os.path.ismount(path1):
  61.             break
  62.         path1 = os.path.dirname(path1)
  63.     while path2 != os.path.dirname(path2):
  64.         if os.path.ismount(path2):
  65.             break
  66.         path2 = os.path.dirname(path2)
  67.     return path1 == path2
  68.  
  69. def version2depends(vinfo):
  70.     if isinstance(vinfo, set):
  71.         vinfo = list(vinfo)
  72.     if isinstance(vinfo, list):
  73.         vinfo = vinfo[:]
  74.         vinfo.sort()
  75.         nv = [int(s) for s in vinfo[-1].split('.')]
  76.         deps = 'python (>= %s), python (<< %d.%d)' % (vinfo[0], nv[0], nv[1]+1)
  77.     elif vinfo in ('all', 'current'):
  78.         supported = [d[6:] for d in pyversions.supported_versions()
  79.                      if re.match(r'python\d\.\d', d)]
  80.         supported.sort()
  81.         deps = 'python (>= %s)' % supported[0]
  82.     elif vinfo == 'current_ext':
  83.         cv = pyversions.default_version(version_only=True)
  84.         nv = [int(s) for s in cv.split('.')]
  85.         deps = 'python (>= %s), python (<< %d.%d)' % (cv, nv[0], nv[1]+1)
  86.     else:
  87.         raise ValueError, 'unknown version info %s' % vinfo
  88.     return deps + ', python-central (>= %s)' % req_pycentral_version
  89.  
  90. def third_party_dir(version):
  91.     if version.startswith('python'):
  92.         version = version[6:]
  93.     if version in ('2.3', '2.4', '2.5'):
  94.         return 'usr/lib/python' + version + '/site-packages'
  95.     else:
  96.         return 'usr/lib/python' + version + '/dist-packages'
  97.  
  98. def build_relative_link(tgt, link):
  99.     t = tgt.split('/')
  100.     l = link.split('/')
  101.     while l[0] == t[0]:
  102.         del l[0], t[0]
  103.     return '/'.join(['..' for i in range(len(l)-1)] + t)
  104.  
  105. def read_dpkg_status(verbose=False):
  106.         """Read the dpkg status file, return a list of packages depending
  107.         on python-central and having a Python-Version information field."""
  108.         packages = []
  109.         rx = re.compile(r'\bpython-central\b')
  110.         pkgname = version = None
  111.         depends = ''
  112.         status = []
  113.         for line in file('/var/lib/dpkg/status'):
  114.             if line.startswith('Package:'):
  115.                 if version != None and 'installed' in status:
  116.                     if 'python-support' in depends:
  117.                         pass
  118.                     elif rx.search(depends):
  119.                         packages.append((pkgname, version))
  120.                         if verbose:
  121.                             print "    %s: %s (%s)" % (pkgname, version, status)
  122.                 version = None
  123.                 status = []
  124.                 pkgname = line.split(':', 1)[1].strip()
  125.             elif line.startswith('Python-Version:'):
  126.                 version = line.split(':', 1)[1].strip()
  127.             elif line.startswith('Depends:'):
  128.                 depends = line.split(':', 1)[1].strip()
  129.             elif line.startswith('Status:'):
  130.                 status = line.split(':', 1)[1].strip().split()
  131.         if version != None and 'installed' in status:
  132.             if rx.search(depends):
  133.                 packages.append((pkgname, version))
  134.                 if verbose:
  135.                     print "    %s: %s (%s)" % (pkgname, version, status)
  136.         return packages
  137.  
  138.  
  139. class PyCentralConfigParser(SafeConfigParser):
  140.     '''SafeConfigParser allowing mixed case, `:' and `=' in keys'''
  141.     OPTCRE = re.compile(
  142.         r'(?P<option>[^\s].*)'
  143.         r'\s*(?P<vi>[:=])\s*'
  144.         r'(?P<value>.*)$'
  145.         )
  146.     optionxform = str
  147.  
  148. class PyCentralError(Exception):
  149.     """Python Central Exception"""
  150.     pass
  151.  
  152. class PyCentralVersionMissingError(PyCentralError):
  153.     """Python Central Version Missing Exception"""
  154.     pass
  155.  
  156. class PythonRuntime:
  157.     def __init__(self, name, version, interp, prefix):
  158.         self.name = name
  159.         self.version = version
  160.         if name.startswith('python'):
  161.             self.short_name = name[6:]
  162.         else:
  163.             self.short_name = name
  164.         self.interp = interp
  165.         if version in ('2.3', '2.4', '2.5'):
  166.             sitedir = 'site-packages/'
  167.         else:
  168.             sitedir = 'dist-packages/'
  169.         if prefix.endswith('/'):
  170.             self.prefix = prefix + sitedir
  171.         else:
  172.             self.prefix = prefix + '/' + sitedir
  173.  
  174.     def byte_compile_dirs(self, dirs, bc_option, exclude=None):
  175.         """call compileall.py -x <exclude regexp> <dirs> according
  176.         to bc_options"""
  177.  
  178.         logging.debug('\tbyte-compile directories')
  179.         errors = False
  180.         cmd = [self.interp, self.prefix + '/compileall.py', '-q']
  181.         if exclude:
  182.             cmd.extend(['-x', exclude])
  183.         cmd.extend(dirs)
  184.         for opt in ('standard', 'optimize'):
  185.             if not opt in bc_option:
  186.                 continue
  187.             if opt == 'optimize':
  188.                 cmd[1:1] = ['-O']
  189.             rv = os.spawnv(os.P_WAIT, self.interp, cmd[1:])
  190.             if rv:
  191.                 raise PyCentralError
  192.  
  193.     def byte_compile(self, files, bc_option, exclude=None, ignore_errors=False, force=False):
  194.         errors = False
  195.         if exclude:
  196.             rx = re.compile(exclude)
  197.             files2 = []
  198.             for fn in files:
  199.                 mo = rx.search(fn)
  200.                 if mo:
  201.                     continue
  202.                 files2.append(fn)
  203.         else:
  204.             files2 = files
  205.         if not files2:
  206.             logging.info('\tno files to byte-compile')
  207.             return
  208.         logging.debug('\tbyte-compile files (%d/%d) %s' \
  209.                       % (len(files), len(files2), self.name))
  210.         debug_files = files2[:min(2, len(files2))]
  211.         if len(files2) > 2:
  212.             debug_files.append('...')
  213.         logging.debug('\t    %s' % debug_files)
  214.         if 'python3' in self.interp:
  215.           cmd = [self.interp, '/usr/bin/py3_compilefiles', '-q']
  216.         else:
  217.           cmd = [self.interp, '/usr/bin/py_compilefiles', '-q']
  218.         if ignore_errors:
  219.             cmd.append('-i')
  220.         if force:
  221.             cmd.append('-f')
  222.         cmd.append('-')
  223.         for opt in ('standard', 'optimize'):
  224.             if not opt in bc_option:
  225.                 continue
  226.             if opt == 'optimize':
  227.                 cmd[1:1] = ['-O']
  228.             try:
  229.                 import subprocess
  230.                 p = subprocess.Popen(cmd, bufsize=1,
  231.                                      shell=False, stdin=subprocess.PIPE)
  232.                 fd = p.stdin
  233.             except ImportError:
  234.                 p = None
  235.                 fd = os.popen(' '.join(cmd), 'w')
  236.             for fn in files2:
  237.                 fd.write(fn + '\n')
  238.             rv = fd.close()
  239.             if p:
  240.                 p.wait()
  241.                 errors = p.returncode != 0
  242.             else:
  243.                 errors = rv != None
  244.             if errors:
  245.                 raise PyCentralError, 'error byte-compiling files (%d)' % len(files2)
  246.  
  247.     def remove_byte_code(self, files):
  248.         errors = False
  249.         logging.debug('\tremove byte-code files (%d)' % (len(files)))
  250.         for ext in ('c', 'o'):
  251.             for fn in files:
  252.                 fnc = fn + ext
  253.                 if os.path.exists(fnc):
  254.                     try:
  255.                         os.unlink(fnc)
  256.                     except OSError, e:
  257.                         print "Sorry", e
  258.                         errors = True
  259.         if errors:
  260.             raise PyCentralError, 'error removing the byte-code files'
  261.  
  262.     def list_byte_code(self, files):
  263.         logging.debug('\tlist byte-code files (%d)' % (len(files)))
  264.         for ext in ('c', 'o'):
  265.             for fn in files:
  266.                 fnc = fn + ext
  267.                 yield fnc
  268.  
  269. installed_runtimes = None
  270. default_runtime = None
  271.  
  272. def get_installed_runtimes(with_unsupported=True):
  273.     global installed_runtimes
  274.     global default_runtime
  275.  
  276.     if not installed_runtimes:
  277.         installed_runtimes = []
  278.         default_version = pyversions.default_version(version_only=True)
  279.         supported = pyversions.supported_versions()
  280.         old = pyversions.old_versions()
  281.         if with_unsupported:
  282.             unsupported = pyversions.unsupported_versions()
  283.         else:
  284.             unsupported = []
  285.         for interp in glob.glob('/usr/bin/python[0-9].[0-9]'):
  286.             if old and os.path.basename(interp) in old:
  287.                 pass
  288.                 #print "INFO: using old version '%s'" % interp
  289.             elif unsupported and os.path.basename(interp) in unsupported:
  290.                 print "INFO: using unsupported version '%s'" % interp
  291.             if not (os.path.basename(interp) in supported
  292.                     or (old and os.path.basename(interp) in old)
  293.                     or (unsupported and os.path.basename(interp) in unsupported)):
  294.                 # pycentral used to ignore everything it has no explicit
  295.                 # knowledge about. this causes install errors when
  296.                 # debian_defaults is not up-to-date (LP: #354228)
  297.                 if with_unsupported:
  298.                     if not interp.startswith('/usr/bin/python3'):
  299.                         print "INFO: using unknown version '%s' (debian_defaults not up-to-date?)" % interp
  300.                 else:
  301.                     print "INFO: ignoring unknown version '%s'" % interp
  302.                     continue
  303.             version = interp[-3:]
  304.             rt = PythonRuntime('python' + version,
  305.                                version,
  306.                                '/usr/bin/python' + version,
  307.                                '/usr/lib/python' + version)
  308.             installed_runtimes.append(rt)
  309.             if version == default_version:
  310.                 default_runtime = rt
  311.     return installed_runtimes
  312.  
  313. def get_default_runtime():
  314.     get_installed_runtimes()
  315.     return default_runtime
  316.  
  317. def get_runtime_for_version(version):
  318.     if version == 'current':
  319.         return get_default_runtime()
  320.     for rt in get_installed_runtimes():
  321.         if rt.version == version:
  322.             return rt
  323.     return None
  324.  
  325. debian_config = None
  326. def get_debian_config():
  327.     global debian_config
  328.     if debian_config is not None:
  329.         return debian_config
  330.  
  331.     config = SafeConfigParser()
  332.     fn = '/etc/python/debian_config'
  333.     if os.path.exists(fn):
  334.         try:
  335.             config.readfp(open(fn))
  336.         except:
  337.             logging.error("error reading config file `%s'" % fn)
  338.             sys.exit(1)
  339.     # checks
  340.     if not config.has_option('DEFAULT', 'byte-compile'):
  341.         config.set('DEFAULT', 'byte-compile', 'standard')
  342.     bc_option = config.get('DEFAULT', 'byte-compile')
  343.     bc_values = set([v.strip() for v in bc_option.split(',')])
  344.     bc_unknown = bc_values - set(['standard', 'optimize'])
  345.     if bc_unknown:
  346.         sys.stderr.write("%s: `%s': unknown values `%s'"
  347.                          " in `byte-compile option'\n"
  348.                          % (program, fn, ', '.join(list(bc_unknown))))
  349.         sys.exit(1)
  350.     config.set('DEFAULT', 'byte-compile', ', '.join(bc_values))
  351.     if config.has_option('DEFAULT', 'overwrite-local'):
  352.         val = config.get('DEFAULT', 'overwrite-local').strip().lower()
  353.         overwrite_local = val in ('yes', '1', 'true')
  354.     else:
  355.         overwrite_local = False
  356.     config.set('DEFAULT', 'overwrite-local', overwrite_local and '1' or '0')
  357.     debian_config = config
  358.     return debian_config
  359.  
  360. class DebPackage:
  361.     def __init__(self, kind, name,
  362.                  oldstyle=False, default_runtime=None,
  363.                  pkgdir=None, pkgconfig=None, parse_versions=True):
  364.         self.kind = kind
  365.         self.name = name
  366.         self.version_field = None
  367.         self.oldstyle = oldstyle
  368.         self.parse_versions = parse_versions
  369.         self.default_runtime = default_runtime
  370.         self.shared_prefix = None
  371.         self.pkgdir = pkgdir
  372.         self.pkgconfig = pkgconfig
  373.         self.has_shared_extension = {}
  374.         self.has_private_extension = False
  375.         self.has_shared_module = {}
  376.         self.has_private_module = False
  377.         if pkgdir:
  378.             self.read_control()
  379.         else:
  380.             self.read_pyfiles()
  381.             #self.print_info()
  382.  
  383.     def read_pyfiles(self):
  384.         self.shared_files = []
  385.         self.pylib_files = {}
  386.         self.other_pylib_files = []
  387.         self.private_files = []
  388.         self.omitted_files = []
  389.         self.pysupport_files = []
  390.         if self.pkgdir:
  391.             lines = []
  392.             for root, dirs, files in os.walk(self.pkgdir):
  393.                 if root.endswith('DEBIAN'):
  394.                     continue
  395.                 if root != self.pkgdir:
  396.                     d = root[len(self.pkgdir):]
  397.                     lines.append(d)
  398.                     for name in files:
  399.                         lines.append(os.path.join(d, name))
  400.                 else:
  401.                     pass # no files in /
  402.         else:
  403.             config_file = '/usr/share/pyshared-data/%s' % self.name
  404.             if self.pkgconfig:
  405.                 lines = [fn for fn, t in self.pkgconfig.items('files')]
  406.                 lines.sort()
  407.             elif os.path.isfile(config_file):
  408.                 logging.debug("reading %s" % config_file)
  409.                 self.pkgconfig = PyCentralConfigParser()
  410.                 self.pkgconfig.optionxform = str
  411.                 self.pkgconfig.readfp(file(config_file))
  412.                 if not self.pkgconfig.has_option('pycentral', 'include-links'):
  413.                     self.pkgconfig.set('pycentral', 'include-links', '0')
  414.                 elif self.pkgconfig.get('pycentral', 'include-links') == '':
  415.                     # LP #332532, empty value in file
  416.                     self.pkgconfig.set('pycentral', 'include-links', '0')
  417.                 lines = [fn for fn, t in self.pkgconfig.items('files')]
  418.                 lines.sort()
  419.             else:
  420.                 lines = self.read_preinst_pkgconfig()
  421.             if lines:
  422.                 pass
  423.             elif os.environ.has_key("PYCENTRAL_NO_DPKG_QUERY"):
  424.                 logging.debug("Not using dpkg-query as requested")
  425.                 lines = map(string.strip, open('/var/lib/dpkg/info/%s.list' % self.name).readlines())
  426.             else:
  427.                 cmd = ['/usr/bin/dpkg-query', '-L', self.name]
  428.                 try:
  429.                     import subprocess
  430.                     p = subprocess.Popen(cmd, bufsize=1,
  431.                                          shell=False, stdout=subprocess.PIPE)
  432.                     fd = p.stdout
  433.                 except ImportError:
  434.                     fd = os.popen(' '.join(cmd))
  435.                 lines = [s[:-1] for s in fd.readlines()]
  436.             if not self.pkgconfig:
  437.                 pc = PyCentralConfigParser()
  438.                 pc.optionxform = str
  439.                 pc.add_section('python-package')
  440.                 pc.set('python-package', 'format', '1')
  441.                 pc.add_section('pycentral')
  442.                 pc.set('pycentral', 'version', req_pycentral_version)
  443.                 pc.set('pycentral', 'include-links', '0')
  444.                 pc.add_section('files')
  445.                 for line in lines:
  446.                     if os.path.isdir(line) and not os.path.islink(line):
  447.                         pc.set('files', line, 'd')
  448.                     elif os.path.exists(line):
  449.                         pc.set('files', line, 'f')
  450.                     else:
  451.                         pass # should not happen
  452.                 self.pkgconfig = pc
  453.  
  454.         old_shared_base = shared_base + self.name + '/site-packages/'
  455.         found_old_base = found_base2 = False
  456.         for line in lines:
  457.             fn = line
  458.             if fn.startswith(shared_base2):
  459.                 # keep _all_ files and directories
  460.                 self.shared_files.append(fn)
  461.                 if fn.endswith('.py'):
  462.                     self.has_shared_module['all'] = True
  463.                 found_base2 = True
  464.                 self.shared_prefix = shared_base2
  465.                 continue
  466.             elif fn.startswith(old_shared_base):
  467.                 # keep _all_ files and directories
  468.                 self.shared_files.append(fn)
  469.                 if fn.endswith('.py'):
  470.                     self.has_shared_module['all'] = True
  471.                 found_old_base = True
  472.                 self.shared_prefix = old_shared_base
  473.                 continue
  474.             elif fn.startswith('/usr/share/python-support') \
  475.                      or fn.startswith('/usr/lib/python-support'):
  476.                 self.pysupport_files.append(fn)
  477.                 continue
  478.             elif not fn.endswith('.py') and not fn.endswith('.so'):
  479.                 if re.match(r'/usr/lib/python\d\.\d/', fn):
  480.                     self.other_pylib_files.append(fn)
  481.                 continue
  482.             elif fn.startswith('/etc/') or fn.startswith('/usr/share/doc/'):
  483.                 # omit files in /etc and /usr/share/doc
  484.                 self.omitted_files.append(fn)
  485.                 continue
  486.             elif re.search(r'/s?bin/', fn):
  487.                 # omit files located in directories
  488.                 self.omitted_files.append(fn)
  489.                 continue
  490.             elif fn.startswith('/usr/lib/site-python/'):
  491.                 version = pyversions.default_version(version_only=True)
  492.                 self.pylib_files.setdefault(version, []).append(fn)
  493.                 continue
  494.             elif re.match(r'/usr/lib/python\d\.\d/', fn):
  495.                 version = fn[15:18]
  496.                 if fn.endswith('.so'):
  497.                     self.has_shared_extension[version] = True
  498.                 if fn.endswith('.py'):
  499.                     self.has_shared_module[version] = True
  500.                     self.pylib_files.setdefault(version, []).append(fn)
  501.                 continue
  502.             else:
  503.                 self.private_files.append(fn)
  504.                 if fn.endswith('.py'):
  505.                     self.has_private_module = True
  506.  
  507.         if found_old_base and found_base2:
  508.             raise PyCentralError, \
  509.                   'shared files found in old (%s) and new locations (%s)' % (old_shared_base, shared_base2)
  510.  
  511.     def read_preinst_pkgconfig(self):
  512.         try:
  513.             fd = open('/var/lib/dpkg/info/%s.preinst' % self.name, 'r')
  514.         except:
  515.             return None
  516.         buffer = None
  517.         for line in fd.readlines():
  518.             if line == '[python-package]\n':
  519.                 buffer = cStringIO.StringIO()
  520.             if line in ('PYEOF\n', 'EOF\n'):
  521.                 break
  522.             if buffer:
  523.                 buffer.write(line)
  524.         if buffer is None:
  525.             return None
  526.         self.pkgconfig = PyCentralConfigParser()
  527.         self.pkgconfig.optionxform = str
  528.         self.pkgconfig.readfp(cStringIO.StringIO(buffer.getvalue()))
  529.         # never set in the preinst copy of the config file
  530.         self.pkgconfig.set('pycentral', 'include-links', '0')
  531.         files = [fn for fn, t in self.pkgconfig.items('files')]
  532.         files.sort()
  533.         return files
  534.  
  535.     def print_info(self, fd=sys.stdout):
  536.         fd.write('Package: %s\n' % self.name)
  537.         fd.write('    shared files  :%4d\n' % len(self.shared_files))
  538.         fd.write('    private files :%4d\n' % len(self.private_files))
  539.         for ver, files in self.pylib_files.items():
  540.             fd.write('    pylib%s files:%4d\n' % (ver, len(files)))
  541.  
  542.     def read_control(self):
  543.         """read the debian/control file, extract the XS-Python-Version
  544.         field; check that XB-Python-Version exists for the package."""
  545.         if not os.path.exists('debian/control'):
  546.             raise PyCentralError, "debian/control not found"
  547.         self.version_field = None
  548.         self.sversion_field = None
  549.         try:
  550.             section = None
  551.             for line in file('debian/control'):
  552.                 line = line.strip()
  553.                 if line == '':
  554.                     section = None
  555.                 elif line.startswith('Source:'):
  556.                     section = 'Source'
  557.                 elif line.startswith('Package: ' + self.name):
  558.                     section = self.name
  559.                 elif line.startswith('XS-Python-Version:'):
  560.                     if section != 'Source':
  561.                         raise PyCentralError, \
  562.                               'attribute XS-Python-Version not in Source section'
  563.                     self.sversion_field = line.split(':', 1)[1].strip()
  564.                 elif line.startswith('XB-Python-Version:'):
  565.                     if section == self.name:
  566.                         self.version_field = line.split(':', 1)[1].strip()
  567.         except:
  568.             pass
  569.         if self.version_field == None:
  570.             raise PyCentralVersionMissingError, \
  571.                   'missing XB-Python-Version attribute in package %s' % self.name
  572.         if self.sversion_field == None:
  573.             raise PyCentralError, 'missing XS-Python-Version attribute'
  574.         if self.parse_versions:
  575.             self.sversion_info = pyversions.parse_versions(self.sversion_field)
  576.         else:
  577.             self.sversion_info = 'all' # dummy
  578.         self.has_private_extension = self.sversion_info == 'current_ext'
  579.  
  580.     def move_files(self):
  581.         """move files from the installation directory to the pycentral location"""
  582.         import shutil
  583.  
  584.         dsttop = self.pkgdir + shared_base2
  585.         try:
  586.             os.makedirs(dsttop)
  587.         except OSError:
  588.             pass
  589.         pversions = pyversions.supported_versions() \
  590.                     + pyversions.unsupported_versions() + pyversions.old_versions()
  591.         pversions = list(set(pversions))
  592.  
  593.         # rename .egg-info files and dirs, remove *.py[co] files
  594.         rename = 'norename' not in os.environ.get('DH_PYCENTRAL', '')
  595.         vrx = re.compile(r'(.*)(-py\d\.\d)(.*)(\.egg-info|\.pth)$')
  596.         for pversion in pversions:
  597.             if pversion in ('python2.3', 'python2.4', 'python2.5'):
  598.                 srctop = os.path.join(self.pkgdir, 'usr/lib', pversion, 'site-packages')
  599.             else:
  600.                 srctop = os.path.join(self.pkgdir, 'usr/lib', pversion, 'dist-packages')
  601.                 # srctop2 and srctop3 are the old/wrong locations, move these.
  602.                 srctop2 = os.path.join(self.pkgdir, 'usr/lib', pversion, 'site-packages')
  603.                 srctop3 = os.path.join(self.pkgdir, 'usr/local/lib', pversion, 'dist-packages')
  604.                 if os.path.isdir(srctop) and (os.path.isdir(srctop2) or os.path.isdir(srctop3)):
  605.                     raise PyCentralError, 'both directories site-packages and dist-packages exist for %s.' % pversion
  606.                 if os.path.isdir(srctop2):
  607.                     print 'renaming %s to %s' % (srctop2, srctop)
  608.                     os.rename(srctop2, srctop)
  609.                 elif os.path.isdir(srctop3):
  610.                     print 'renaming %s to %s' % (srctop3, srctop)
  611.                     try:
  612.                         os.makedirs(os.path.dirname(srctop))
  613.                     except OSError:
  614.                         pass
  615.                     os.rename(srctop3, srctop)
  616.                     while srctop3:
  617.                         srctop3 = os.path.dirname(srctop3)
  618.                         try:
  619.                             os.rmdir(srctop3)
  620.                         except OSError:
  621.                             break
  622.                 # do the same with -dbg packages (which don't have an Python-Version attribute)
  623.                 pkgdbgdir = self.pkgdir + '-dbg'
  624.                 if os.path.isdir(pkgdbgdir):
  625.                     srcdtop = os.path.join(pkgdbgdir, 'usr/lib', pversion, 'dist-packages')
  626.                     srcdtop2 = os.path.join(pkgdbgdir, 'usr/lib', pversion, 'site-packages')
  627.                     srcdtop3 = os.path.join(pkgdbgdir, 'usr/local/lib', pversion, 'dist-packages')
  628.                     if os.path.isdir(srcdtop) and (os.path.isdir(srcdtop2) or os.path.isdir(srcdtop3)):
  629.                         print 'both directories site-packages and dist-packages exist for %s-dbg.' % pversion
  630.                     if os.path.isdir(srcdtop2):
  631.                         print 'renaming %s to %s' % (srcdtop2, srcdtop)
  632.                         os.rename(srcdtop2, srcdtop)
  633.                     elif os.path.isdir(srcdtop3):
  634.                         print 'renaming %s to %s' % (srcdtop3, srcdtop)
  635.                         try:
  636.                             os.makedirs(os.path.dirname(srcdtop))
  637.                         except OSError:
  638.                             pass
  639.                         os.rename(srcdtop3, srcdtop)
  640.                         while srcdtop3:
  641.                             srcdtop3 = os.path.dirname(srcdtop3)
  642.                             try:
  643.                                 os.rmdir(srcdtop3)
  644.                             except OSError:
  645.                                 break
  646.                     srcdtop = os.path.join(pkgdbgdir, 'usr/lib/debug/usr/lib', pversion, 'dist-packages')
  647.                     srcdtop2 = os.path.join(pkgdbgdir, 'usr/lib/debug/usr/lib', pversion, 'site-packages')
  648.                     srcdtop3 = os.path.join(pkgdbgdir, 'usr/lib/debug/usr/local/lib', pversion, 'dist-packages')
  649.                     if os.path.isdir(srcdtop) and (os.path.isdir(srcdtop2) or os.path.isdir(srcdtop3)):
  650.                         print 'both directories site-packages and dist-packages exist.'
  651.                     if os.path.isdir(srcdtop2):
  652.                         print 'renaming %s to %s' % (srcdtop2, srcdtop)
  653.                         os.rename(srcdtop2, srcdtop)
  654.                     elif os.path.isdir(srcdtop3):
  655.                         print 'renaming %s to %s' % (srcdtop3, srcdtop)
  656.                         try:
  657.                             os.makedirs(os.path.dirname(srcdtop))
  658.                         except OSError:
  659.                             pass
  660.                         os.rename(srcdtop3, srcdtop)
  661.                         while srcdtop3:
  662.                             srcdtop3 = os.path.dirname(srcdtop3)
  663.                             try:
  664.                                 os.rmdir(srcdtop3)
  665.                             except OSError:
  666.                                 break
  667.             for root, dirs, files in os.walk(srctop, topdown=False):
  668.                 for name in files:
  669.                     m = vrx.match(name)
  670.                     if m and rename:
  671.                         name2 = ''.join(m.group(1, 3, 4))
  672.                         print "rename: %s -> %s" % (name, name2)
  673.                         os.rename(os.path.join(root, name), os.path.join (root, name2))
  674.                     elif name.endswith('.pyc') or name.endswith('.pyo'):
  675.                         os.unlink(os.path.join(root, name))
  676.                 for name in dirs:
  677.                     m = vrx.match(name)
  678.                     if m and rename:
  679.                         name2 = ''.join(m.group(1, 3, 4))
  680.                         print "rename: %s -> %s" % (name, name2)
  681.                         os.rename(os.path.join(root, name), os.path.join (root, name2))
  682.  
  683.         # search for differences
  684.         import filecmp
  685.         class MyDircmp(filecmp.dircmp):
  686.             def report(self):
  687.                 if self.left_only or self.right_only or self.diff_files or self.funny_files:
  688.                     self.differs = True
  689.                     print 'diff', self.left, self.right
  690.                 if self.left_only:
  691.                     self.left_only.sort()
  692.                     print 'Only in', self.left, ':', self.left_only
  693.                 if self.right_only:
  694.                     self.right_only.sort()
  695.                     print 'Only in', self.right, ':', self.right_only
  696.                 if self.diff_files:
  697.                     self.diff_files.sort()
  698.                     print 'Differing files :', self.diff_files
  699.                 if self.funny_files:
  700.                     self.funny_files.sort()
  701.                     print 'Trouble with common files :', self.funny_files
  702.  
  703.         for pv1 in pversions:
  704.             for pv2 in pversions[pversions.index(pv1)+1:]:
  705.                 site1 = os.path.join(self.pkgdir, third_party_dir(pv1))
  706.                 site2 = os.path.join(self.pkgdir, third_party_dir(pv2))
  707.                 if not (os.path.isdir(site1) and os.path.isdir(site2)):
  708.                     continue
  709.                 dco = MyDircmp(site1, site2)
  710.                 dco.differs = False
  711.                 dco.report_full_closure()
  712.  
  713.         # move around
  714.         for pversion in pversions:
  715.             srctop = os.path.join(self.pkgdir, third_party_dir(pversion))
  716.             for root, dirs, files in os.walk(srctop):
  717.                 if root == srctop:
  718.                     d = '.'
  719.                 else:
  720.                     d = root[len(srctop)+1:]
  721.                 for name in dirs:
  722.                     src = os.path.join(root, name)
  723.                     dst = os.path.join(dsttop, d, name)
  724.                     try:
  725.                         os.mkdir(dst)
  726.                         shutil.copymode(src, dst)
  727.                     except OSError:
  728.                         pass
  729.                 for name in files:
  730.                     src = os.path.join(root, name)
  731.                     dst = os.path.join(dsttop, d, name)
  732.                     if re.search(r'\.so(\.\d+)*?$', name):
  733.                         continue
  734.                     # TODO: if dst already exists, make sure, src == dst
  735.                     os.rename(src, dst)
  736.             # remove empty dirs in /usr/lib/pythonX.Y
  737.             for root, dirs, files in os.walk(self.pkgdir + '/usr/lib', topdown=False):
  738.                 try:
  739.                     if re.match("/usr/lib/python\d\.\d($|/)", root.replace(self.pkgdir, "")):
  740.                         os.rmdir(root)
  741.                 except OSError:
  742.                     pass
  743.             try:
  744.                 os.rmdir(self.pkgdir + '/usr/lib')
  745.             except OSError:
  746.                 pass
  747.         # remove empty dirs in /usr/share/pyshared
  748.         for root, dirs, files in os.walk(self.pkgdir + shared_base2, topdown=False):
  749.             try:
  750.                 os.rmdir(root)
  751.             except OSError:
  752.                 pass
  753.         try:
  754.             os.rmdir(self.pkgdir + shared_base2)
  755.         except OSError:
  756.             pass
  757.  
  758.     def add_shared_links(self):
  759.         versions = pyversions.requested_versions(self.sversion_field, True)
  760.         tgttop = os.path.join(self.pkgdir, shared_base2.strip('/'))
  761.         existing_links = []
  762.         for version in versions:
  763.             linktop = os.path.join(self.pkgdir, third_party_dir(version))
  764.             try:
  765.                 os.makedirs(linktop)
  766.             except OSError:
  767.                 pass
  768.             for root, dirs, files in os.walk(tgttop, topdown=True):
  769.                 linkroot = root.replace(tgttop, linktop, 1)
  770.                 for name in files:
  771.                     tgt = os.path.join(root, name)
  772.                     link = os.path.join(linkroot, name)
  773.                     if os.path.exists(link):
  774.                         existing_links.append(link)
  775.                     else:
  776.                         rel_tgt = build_relative_link(tgt, link)
  777.                         os.symlink(rel_tgt, link)
  778.                 for name in dirs:
  779.                     try:
  780.                         os.makedirs(os.path.join(linkroot, name))
  781.                     except OSError:
  782.                         pass
  783.         if existing_links:
  784.             print 'Found existing files while including symlinks:'
  785.             for link in existing_links:
  786.                 print '  ', link
  787.             raise PyCentralError, 'Found existing files while including symlinks'
  788.  
  789.     def gen_substvars(self):
  790.         supported = [d[6:] for d in pyversions.supported_versions()
  791.                      if re.match(r'python\d\.\d', d)]
  792.         versions = ''
  793.         prversions = ''
  794.         self.depends = None
  795.         if len(self.has_shared_module) or len(self.has_shared_extension):
  796.             # shared modules / extensions
  797.             if len(self.has_shared_extension):
  798.                 versions = self.has_shared_extension.keys()
  799.             else:
  800.                 if self.sversion_info in ('current', 'current_ext'):
  801.                     versions = 'current'
  802.                 elif self.sversion_info == 'all':
  803.                     versions = 'all'
  804.                     prversions = supported
  805.                 else:
  806.                     versions = self.sversion_field
  807.                     prversions = list(self.sversion_info.intersection(supported))
  808.                     self.depends = version2depends(self.sversion_info)
  809.         elif self.has_private_module or self.has_private_extension:
  810.             if self.sversion_info == 'all':
  811.                 versions = 'current'
  812.             elif self.sversion_info == 'current':
  813.                 versions = 'current'
  814.             elif self.sversion_info == 'current_ext':
  815.                 versions = [pyversions.default_version(version_only=True)]
  816.             elif isinstance(self.sversion_info, list) or isinstance(self.sversion_info, set):
  817.                 # exact version info required, no enumeration, no relops
  818.                 if len(self.sversion_info) != 1 or not re.match(r'\d\.\d', self.sversion_info[0]):
  819.                     raise PyCentralError, 'no exact version for package with private modules'
  820.                 versions = [list(self.sversion_info)[0]]
  821.             else:
  822.                 raise PyCentralError, 'version error for package with private modules'
  823.         else:
  824.             # just "copy" it from the source field
  825.             if self.sversion_info == 'current':
  826.                 versions = 'current'
  827.             elif self.sversion_info == 'current_ext':
  828.                 versions = [pyversions.default_version(version_only=True)]
  829.             elif self.sversion_info == 'all':
  830.                 versions = 'all'
  831.                 prversions = supported
  832.             else:
  833.                 versions = self.sversion_field
  834.                 prversions = list(self.sversion_info.intersection(supported))
  835.                 self.depends = version2depends(self.sversion_info)
  836.  
  837.         if (len(self.has_shared_module) or len(self.has_shared_extension)) \
  838.            and self.has_private_module or self.has_private_extension:
  839.             # use case? use the information for the shared stuff
  840.             pass
  841.         if versions == '':
  842.             raise PyCentralError, 'unable to determine Python-Version attribute'
  843.         if isinstance(versions, list) or isinstance(versions, set):
  844.             self.version_field = ', '.join(versions)
  845.         else:
  846.             self.version_field = versions
  847.         if not self.depends:
  848.             self.depends = version2depends(versions)
  849.         if self.name.startswith('python-'):
  850.             if prversions == '':
  851.                 prversions = versions
  852.             self.provides = ', '.join([self.name.replace('python-', 'python%s-' % ver)
  853.                                        for ver in prversions])
  854.  
  855.     def set_version_field(self, version_field):
  856.         self.version_field = version_field
  857.         if self.parse_versions:
  858.             self.version_info = pyversions.parse_versions(version_field)
  859.  
  860.     def read_version_info(self, use_default_if_missing=False):
  861.         """Read the Python-Version information field"""
  862.         if self.version_field:
  863.             return
  864.         if self.pkgconfig and self.pkgconfig.has_option('python-package', 'python-version'):
  865.             self.version_field = self.pkgconfig.get('python-package', 'python-version')
  866.             logging.debug("Using python-version from pkgconfig: %s" % self.version_field)
  867.         elif os.environ.has_key("PYCENTRAL_NO_DPKG_QUERY"):
  868.             logging.debug("Not using dpkg-query as requested")
  869.             needle = "Package: %s\n" % self.name
  870.             for block in open("/var/lib/dpkg/status").read().split("\n\n"):
  871.                 if needle in block:
  872.                     for line in block.split("\n"):
  873.                         if line.startswith('Python-Version:'):
  874.                             self.version_field = line.split(':', 1)[1].strip()
  875.                             break
  876.         else:
  877.             logging.debug("dpkg-query -s %s" % self.name)
  878.             cmd = ['/usr/bin/dpkg-query', '-s', self.name]
  879.             try:
  880.                 import subprocess
  881.                 p = subprocess.Popen(cmd, bufsize=1,
  882.                                      shell=False, stdout=subprocess.PIPE)
  883.                 fd = p.stdout
  884.             except ImportError:
  885.                 fd = os.popen(' '.join(cmd))
  886.  
  887.             for line in fd:
  888.                 if line.startswith('Python-Version:'):
  889.                     self.version_field = line.split(':', 1)[1].strip()
  890.                     break
  891.             fd.close()
  892.         # now verify/parse it
  893.         if not self.version_field and use_default_if_missing:
  894.             logging.warn("%s: has no Python-Version field, assuming default runtime" % self.name)
  895.             self.version_field = get_default_runtime().version
  896.         if not self.version_field:
  897.             raise PyCentralError, "package has no field Python-Version"
  898.         if self.parse_versions:
  899.             self.version_info = pyversions.parse_versions(self.version_field)
  900.  
  901.     def set_default_runtime_from_version_info(self):
  902.         versions = list(pyversions.requested_versions(self.version_field, version_only=True))
  903.         if not versions:
  904.             #raise PyCentralError, "no matching runtime for `%s'" % self.version_field
  905.             logging.warn("%s: no matching runtime for `%s', using default"
  906.                          % (self.name, self.version_field))
  907.             self.default_runtime = get_default_runtime()
  908.         if len(versions) == 1:
  909.             self.default_runtime = get_runtime_for_version(versions[0])
  910.         elif pyversions.default_version(version_only=True) in versions:
  911.             self.default_runtime = get_default_runtime()
  912.         else:
  913.             self.default_runtime = get_runtime_for_version(versions[0])
  914.  
  915.     def byte_compile(self, runtimes, bc_option, exclude_regex, ignore_errors=False):
  916.         """byte compiles all files not handled by pycentral"""
  917.  
  918.         logging.debug("    byte-compile %s" % self.name)
  919.         if self.shared_files:
  920.             ppos = len(self.shared_prefix)
  921.             for rt in runtimes:
  922.                 linked_files = [ rt.prefix + fn[ppos:]
  923.                                  for fn in self.shared_files
  924.                                  if fn[-3:] == '.py']
  925.                 rt.byte_compile(linked_files, bc_option, exclude_regex, ignore_errors)
  926.  
  927.         for pyver, files in self.pylib_files.items():
  928.             logging.debug("bc for v%s (%d files)" % (pyver, len(files)))
  929.             rt = get_runtime_for_version(pyver)
  930.             if rt in runtimes:
  931.                 rt.byte_compile(files, bc_option, exclude_regex)
  932.  
  933.         if self.private_files:
  934.             logging.debug("bc %s private (%d files)" %
  935.                           (self.default_runtime.version, len(self.private_files)))
  936.             rt = self.default_runtime
  937.             rt.byte_compile(self.private_files, bc_option, exclude_regex)
  938.  
  939.     def remove_bytecode(self):
  940.         """remove all byte-compiled files not handled by pycentral"""
  941.  
  942.         assert self.oldstyle
  943.  
  944.         logging.debug("    remove byte-code for %s" % self.name)
  945.         pyfiles = []
  946.         for files in self.pylib_files.values():
  947.             pyfiles.extend(files)
  948.         pyfiles.extend(self.private_files)
  949.  
  950.         errors = False
  951.         for ext in ('c', 'o'):
  952.             for fn in pyfiles:
  953.                 fnc = fn + ext
  954.                 if os.path.exists(fnc):
  955.                     try:
  956.                         os.unlink(fnc)
  957.                     except OSError, e:
  958.                         print "Sorry", e
  959.                         errors = True
  960.         if errors:
  961.             raise PyCentralError
  962.  
  963.     def link_shared_files(self, rt):
  964.         """link shared files at runtime"""
  965.         #if samefs(rt.prefix, self.shared_files[0]):
  966.         #    link_cmd = os.link
  967.         #else:
  968.         #    link_cmd = os.symlink
  969.         logging.debug("\tlink shared files %s/%s" % (rt.name, self.name))
  970.         if not self.shared_files:
  971.             return []
  972.         link_cmd = os.symlink
  973.         ppos = len(self.shared_prefix)
  974.         existing_files = []
  975.         for fn in self.shared_files:
  976.             fn2 = rt.prefix + fn[ppos:]
  977.             if os.path.isdir(fn) and not os.path.islink(fn):
  978.                 continue
  979.             if os.path.exists(fn2):
  980.                 link = abs_link = None
  981.                 if os.path.islink(fn2):
  982.                     link = abs_link = os.readlink(fn2)
  983.                     if link.startswith('../'):
  984.                         abs_link = os.path.normpath(os.path.join(os.path.dirname(fn2), link))
  985.                 if abs_link == fn or link == fn:
  986.                     continue
  987.                 if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
  988.                     existing_files.append(fn2)
  989.  
  990.         if existing_files:
  991.             conf = get_debian_config()
  992.             overwrite_local = conf.get('DEFAULT', 'overwrite-local') == '1'
  993.             if overwrite_local:
  994.                 print '\n  '.join(["overwriting local files:"] + existing_files)
  995.  
  996.         linked_files = []
  997.         try:
  998.             for fn in self.shared_files:
  999.                 fn2 = rt.prefix + fn[ppos:]
  1000.                 if os.path.isdir(fn) and not os.path.islink(fn):
  1001.                     if os.path.isdir(fn2):
  1002.                         continue
  1003.                     os.makedirs(fn2)
  1004.                     linked_files.append(fn2)
  1005.                 else:
  1006.                     if os.path.exists(fn2):
  1007.                         msg = "already exists: %s" % fn2
  1008.                         link = abs_link = None
  1009.                         if os.path.islink(fn2):
  1010.                             link = abs_link = os.readlink(fn2)
  1011.                             if link.startswith('../'):
  1012.                                 abs_link = os.path.normpath(os.path.join(os.path.dirname(fn2), link))
  1013.                         if abs_link == fn or link == fn:
  1014.                             linked_files.append(fn2)
  1015.                             continue
  1016.                         if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
  1017.                             msg = msg + " -> %s" % link
  1018.                             if overwrite_local:
  1019.                                 print "warning:", msg
  1020.                                 os.unlink(fn2)
  1021.                             else:
  1022.                                 continue # raise PyCentralError, msg at end of method
  1023.                     # make sure that fn2 really does not exist; this is a
  1024.                     # special hack to make pycentral work with fakechroot,
  1025.                     # which has a slightly weird treatment of symlinks
  1026.                     # now needed to switch between old and new prefix
  1027.                     try:
  1028.                         os.unlink(fn2)
  1029.                     except OSError:
  1030.                         pass
  1031.                     link_cmd(fn, fn2)
  1032.                     linked_files.append(fn2)
  1033.         except PyCentralError, msg:
  1034.             raise
  1035.         except Exception, msg:
  1036.             print msg
  1037.             # FIXME: undo
  1038.             linked_files.reverse()
  1039.             return []
  1040.         else:
  1041.             if existing_files and not overwrite_local:
  1042.                 s = get_file_overwrite_error(existing_files)
  1043.                 raise PyCentralError, s
  1044.             return linked_files
  1045.  
  1046.     def unlink_shared_files(self, rt):
  1047.         logging.debug('\tunlink_shared_files %s/%s' % (rt.name, self.name))
  1048.         if not self.shared_files:
  1049.             return
  1050.         ppos = len(self.shared_prefix)
  1051.         shared_files = self.shared_files[:]
  1052.         shared_files.reverse()
  1053.         for fn in shared_files:
  1054.             fn2 = rt.prefix + fn[ppos:]
  1055.             if os.path.isdir(fn2) and not os.path.islink(fn2):
  1056.                 try:
  1057.                     os.removedirs(fn2)
  1058.                 except OSError:
  1059.                     pass
  1060.             else:
  1061.                 if os.path.exists(fn2):
  1062.                     os.unlink(fn2)
  1063.  
  1064.     def list_shared_files(self, rt):
  1065.         logging.debug('\tlist_shared_files %s/%s' % (rt.name, self.name))
  1066.         if not self.shared_files:
  1067.             return
  1068.         ppos = len(self.shared_prefix)
  1069.         for fn in self.shared_files:
  1070.             fn2 = rt.prefix + fn[ppos:]
  1071.             yield fn2
  1072.  
  1073.  
  1074.     def install(self, runtimes, bc_option, exclude_regex,
  1075.                 byte_compile_default=True, ignore_errors=False):
  1076.         logging.debug('\tinstall package %s' % self.name)
  1077.         # install shared .py files
  1078.         symlinks_included = self.pkgconfig.getboolean('pycentral', 'include-links')
  1079.         if symlinks_included:
  1080.             pass
  1081.         else:
  1082.             if self.shared_files:
  1083.                 for rt in runtimes:
  1084.                     linked_files = self.link_shared_files(rt)
  1085.                     rt.byte_compile(linked_files, bc_option, exclude_regex, ignore_errors)
  1086.         # byte compile files inside prefix
  1087.         if self.pylib_files:
  1088.             for pyver, files in self.pylib_files.items():
  1089.                 rt = get_runtime_for_version(pyver)
  1090.                 if rt in runtimes:
  1091.                     rt.byte_compile(files, bc_option, exclude_regex, ignore_errors)
  1092.         # byte compile with the default runtime for the package
  1093.         if byte_compile_default:
  1094.             if self.private_files:
  1095.                 self.default_runtime.byte_compile(self.private_files, bc_option,
  1096.                                                   exclude_regex, ignore_errors)
  1097.  
  1098.     def prepare(self, runtimes, old_runtimes, old_pkg, ignore_errors=False):
  1099.         logging.debug('\tprepare package %s' % self.name)
  1100.  
  1101.         ppos = len(self.shared_prefix)
  1102.  
  1103.         if old_pkg and old_pkg.private_files:
  1104.             fs_in_old = set([fn for fn in old_pkg.private_files if fn[-3:] == '.py'])
  1105.             fs_in_new = set([fn for fn in self.private_files if fn[-3:] == '.py'])
  1106.             removed_fs = list(fs_in_old.difference(fs_in_new))
  1107.             if removed_fs:
  1108.                 logging.debug_list('\t', 'removed private', removed_fs)
  1109.                 default_runtime.remove_byte_code(removed_fs)
  1110.  
  1111.         old_pylib_fs = []
  1112.         if old_pkg and old_pkg.pylib_files:
  1113.             for pyver, files in old_pkg.pylib_files.items():
  1114.                 fs_in_old = set([fn for fn in files if fn[-3:] == '.py'])
  1115.                 fs_in_new = set([fn for fn in self.pylib_files.get(pyver, []) if fn[-3:] == '.py'])
  1116.                 removed_fs = list(fs_in_old.difference(fs_in_new))
  1117.                 if removed_fs:
  1118.                     logging.debug_list('\t', 'removed pylib', removed_fs)
  1119.                     default_runtime.remove_byte_code(removed_fs)
  1120.                 old_pylib_fs += files
  1121.             old_pylib_fs += old_pkg.other_pylib_files
  1122.  
  1123.         if old_pkg and old_pkg.shared_files:
  1124.             for rt in old_runtimes:
  1125.                 if rt in runtimes:
  1126.                     continue
  1127.                 linked_files = [ rt.prefix + fn[ppos:]
  1128.                                  for fn in old_pkg.shared_files if fn[-3:] == '.py']
  1129.                 if linked_files:
  1130.                     logging.debug_list('\t', 'removed runtimes', linked_files)
  1131.                     default_runtime.remove_byte_code(linked_files)
  1132.                     self.unlink_files(rt)
  1133.  
  1134.         if not self.shared_files:
  1135.             return
  1136.  
  1137.         dirs_in_new = set([fn for fn, t in self.pkgconfig.items('files')
  1138.                            if t == 'd' if fn.startswith(self.shared_prefix)])
  1139.         dirs_in_old = set()
  1140.         if old_pkg:
  1141.             dirs_in_old = set([fn for fn, t in old_pkg.pkgconfig.items('files')
  1142.                                if t == 'd' if fn.startswith(self.shared_prefix)])
  1143.         new_dirs = list(dirs_in_new.difference(dirs_in_old))
  1144.         new_dirs.sort()
  1145.         removed_dirs = list(dirs_in_old.difference(dirs_in_new))
  1146.         removed_dirs.sort()
  1147.  
  1148.         fs_in_new = set([fn for fn, t in self.pkgconfig.items('files')
  1149.                          if t == 'f' if fn.startswith(self.shared_prefix)])
  1150.         fs_in_old = set()
  1151.         if old_pkg:
  1152.             fs_in_old = set([fn for fn, t in old_pkg.pkgconfig.items('files')
  1153.                              if t == 'f' if fn.startswith(self.shared_prefix)])
  1154.         new_fs = list(fs_in_new.difference(fs_in_old))
  1155.         new_fs.sort()
  1156.         removed_fs = list(fs_in_old.difference(fs_in_new))
  1157.         removed_fs.sort()
  1158.  
  1159.         logging.debug_list('\t', 'new      dirs', new_dirs)
  1160.         logging.debug_list('\t', 'removed  dirs', removed_dirs)
  1161.         logging.debug_list('\t', 'new     files', new_fs)
  1162.         logging.debug_list('\t', 'removed files', removed_fs)
  1163.  
  1164.         link_cmd = os.symlink
  1165.         ppos = len(self.shared_prefix)
  1166.         existing_files = []
  1167.         for rt in runtimes:
  1168.             for f1 in new_fs:
  1169.                 f2 = rt.prefix + f1[ppos:]
  1170.                 if os.path.exists(f2):
  1171.                     link = abs_link = None
  1172.                     if os.path.islink(f2):
  1173.                         link = abs_link = os.readlink(f2)
  1174.                         if link.startswith('../'):
  1175.                             abs_link = os.path.normpath(os.path.join(os.path.dirname(f2), link))
  1176.                     if abs_link == f1 or link == f1:
  1177.                         continue
  1178.                     if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
  1179.                         existing_files.append(f2)
  1180.         if existing_files:
  1181.             # if the current installed version does not use python-central
  1182.             # then having a file here is expected and harmless
  1183.             if not self.name in [p for (p,v) in read_dpkg_status()]:
  1184.                 logging.info("%s: upgrade from package version not using python-central" % self.name)
  1185.                 return
  1186.             # if all existing files are found in the old package in
  1187.             # /usr/lib/pythonX.Y/site-packages, and moved to the shared area,
  1188.             # do nothing.
  1189.             not_in_same_pkg = set(existing_files)
  1190.             if old_pkg:
  1191.                 not_in_same_pkg.difference_update(old_pylib_fs)
  1192.             if not_in_same_pkg == set():
  1193.                 logging.info("%s: upgrade from package version with unmoved files" % self.name)
  1194.                 return
  1195.             conf = get_debian_config()
  1196.             overwrite_local = conf.get('DEFAULT', 'overwrite-local') == '1'
  1197.             if overwrite_local:
  1198.                 print "overwriting local files"
  1199.  
  1200.         for rt in runtimes:
  1201.             for d1 in new_dirs:
  1202.                 d2 = rt.prefix + d1[ppos:]
  1203.                 if os.path.isdir(d2):
  1204.                     continue
  1205.                 os.makedirs(d2)
  1206.             for f1 in new_fs:
  1207.                 f2 = rt.prefix + f1[ppos:]
  1208.                 if os.path.exists(f2):
  1209.                     msg = "already exists: %s" % f2
  1210.                     link = abs_link = None
  1211.                     if os.path.islink(f2):
  1212.                         link = abs_link = os.readlink(f2)
  1213.                         if link.startswith('../'):
  1214.                             abs_link = os.path.normpath(os.path.join(os.path.dirname(f2), link))
  1215.                     if abs_link == f1 or link == f1:
  1216.                         continue
  1217.                     if not link or not (abs_link.startswith(shared_base2) or abs_link.startswith(shared_base)):
  1218.                         msg = msg + " -> %s" % link
  1219.                         if overwrite_local:
  1220.                             print "warning:", msg
  1221.                             os.unlink(f2)
  1222.                         else:
  1223.                             continue # raise PyCentralError, msg at end of loop
  1224.                 # hack to make pycentral work with fakechroot
  1225.                 # now needed to switch between old and new prefix
  1226.                 try:
  1227.                     os.unlink(f2)
  1228.                 except OSError:
  1229.                     pass
  1230.                 try:
  1231.                     d = os.path.dirname(f2)
  1232.                     if not os.path.isdir(d):
  1233.                         print "create directory %s" % d
  1234.                         os.makedirs(d)
  1235.                     link_cmd(f1, f2)
  1236.                 except OSError:
  1237.                     print "unable to create symlink %s" % f2
  1238.                     raise
  1239.         if existing_files and not overwrite_local:
  1240.             raise PyCentralError, "not overwriting local files"
  1241.  
  1242.         for rt in runtimes:
  1243.             for f1 in removed_fs:
  1244.                 f2 = rt.prefix + f1[ppos:]
  1245.                 try:
  1246.                     os.unlink(f2)
  1247.                     os.unlink(f2 + 'c')
  1248.                     os.unlink(f2 + 'o')
  1249.                 except OSError:
  1250.                     pass
  1251.             for d1 in removed_dirs:
  1252.                 d2 = rt.prefix + d1[ppos:]
  1253.                 try:
  1254.                     os.rmdir(d2)
  1255.                 except OSError:
  1256.                     pass
  1257.  
  1258.         return
  1259.  
  1260.     def remove(self, runtimes, remove_script_files=True):
  1261.         logging.debug('\tremove package %s' % self.name)
  1262.         # remove shared .py files
  1263.         symlinks_included = self.pkgconfig.getboolean('pycentral', 'include-links')
  1264.         if symlinks_included:
  1265.             pass
  1266.         else:
  1267.             if self.shared_files:
  1268.                 ppos = len(self.shared_prefix)
  1269.                 for rt in runtimes:
  1270.                     linked_files = [ rt.prefix + fn[ppos:]
  1271.                                      for fn in self.shared_files
  1272.                                      if fn[-3:] == '.py']
  1273.                     #print self.shared_files
  1274.                     #print linked_files
  1275.                     default_runtime.remove_byte_code(linked_files)
  1276.                     self.unlink_shared_files(rt)
  1277.         # remove byte compiled files inside prefix
  1278.         if self.pylib_files:
  1279.             for pyver, files in self.pylib_files.items():
  1280.                 rt = get_runtime_for_version(pyver)
  1281.                 if rt in runtimes:
  1282.                     default_runtime.remove_byte_code(files)
  1283.         # remove byte code for script files
  1284.         if remove_script_files:
  1285.             if self.private_files:
  1286.                 default_runtime.remove_byte_code(self.private_files)
  1287.  
  1288.     def list(self, runtimes, list_script_files=True):
  1289.         logging.debug('\tlist package %s' % self.name)
  1290.         # list shared .py files
  1291.         symlinks_included = self.pkgconfig.getboolean('pycentral', 'include-links')
  1292.         if not symlinks_included and self.shared_files:
  1293.             ppos = len(self.shared_prefix)
  1294.             for rt in runtimes:
  1295.                 linked_files = [ rt.prefix + fn[ppos:]
  1296.                                  for fn in self.shared_files
  1297.                                  if fn[-3:] == '.py']
  1298.                 for f in default_runtime.list_byte_code(linked_files):
  1299.                     yield f
  1300.                 for f in self.list_shared_files(rt):
  1301.                     yield f
  1302.         # list byte compiled files inside prefix
  1303.         if self.pylib_files:
  1304.             for pyver, files in self.pylib_files.items():
  1305.                 rt = get_runtime_for_version(pyver)
  1306.                 if rt in runtimes:
  1307.                     for f in default_runtime.list_byte_code(files):
  1308.                         yield f
  1309.         # list byte code for script files
  1310.         if list_script_files:
  1311.             if self.private_files:
  1312.                 for f in default_runtime.list_byte_code(self.private_files):
  1313.                     yield f
  1314.  
  1315.     def update_bytecode_files(self, runtimes, rt_default, bc_option):
  1316.         # byte-compile with default python version
  1317.         logging.debug('\tupdate byte-code for %s' % self.name)
  1318.         exclude_regex = None
  1319.         # update shared .py files
  1320.         if self.shared_files:
  1321.             ppos = len(self.shared_prefix)
  1322.             for rt in runtimes:
  1323.                 if rt == rt_default:
  1324.                     linked_files = self.link_shared_files(rt)
  1325.                     rt.byte_compile(linked_files, bc_option, exclude_regex)
  1326.                 else:
  1327.                     linked_files = [ rt.prefix + fn[ppos:]
  1328.                                      for fn in self.shared_files
  1329.                                      if fn[-3:] == '.py']
  1330.                     rt.remove_byte_code(linked_files)
  1331.                     self.unlink_shared_files(rt)
  1332.         # byte compile with the default runtime for the package
  1333.         if self.private_files:
  1334.             self.default_runtime.byte_compile(self.private_files,
  1335.                                               bc_option, exclude_regex, force=True)
  1336.  
  1337. known_actions = {}
  1338. def register_action(action_class):
  1339.     known_actions[action_class.name] = action_class
  1340.  
  1341. class Action:
  1342.     _option_parser = None
  1343.     name = None
  1344.     help = ""
  1345.     usage = "<options>"
  1346.     def __init__(self):
  1347.         self.errors_occured = 0
  1348.         parser = self.get_option_parser()
  1349.         parser.set_usage(
  1350.             'usage: %s [<options> ...] %s %s' % (program, self.name, self.usage))
  1351.  
  1352.     def get_option_parser(self):
  1353.         if not self._option_parser:
  1354.             p = OptionParser()
  1355.             self._option_parser = p
  1356.         return self._option_parser
  1357.  
  1358.     def info(self, msg, stream=sys.stderr):
  1359.         logging.info('%s %s: %s' % (program, self.name, msg))
  1360.  
  1361.     def warn(self, msg, stream=sys.stderr):
  1362.         logging.warn('%s %s: %s' % (program, self.name, msg))
  1363.  
  1364.     def error(self, msg, stream=sys.stderr, go_on=False):
  1365.         logging.error('%s %s: %s' % (program, self.name, msg))
  1366.         self.errors_occured += 1
  1367.         if not go_on:
  1368.             sys.exit(1)
  1369.  
  1370.     def parse_args(self, arguments):
  1371.         self.options, self.args = self._option_parser.parse_args(arguments)
  1372.         return self.options, self.args
  1373.  
  1374.     def check_args(self, global_options):
  1375.         return self.errors_occured
  1376.  
  1377.     def run(self, global_opts):
  1378.         pass
  1379.  
  1380.  
  1381. class ActionByteCompile(Action):
  1382.     """byte compile the *.py files in <package> using the the
  1383.     default python version (or use the version specified with -v.
  1384.     Any additional directory arguments are ignored (only files
  1385.     found in the package are byte compiled. Files in
  1386.     /usr/lib/pythonX.Y are compiled with the matching python version.
  1387.  
  1388.     bccompile is a replacement for the current byte compilation
  1389.     generated by the dh_python debhelper script.
  1390.     """
  1391.     name = 'bccompile'
  1392.     help = 'byte compile .py files in a package'
  1393.     usage = '[<options>] <package> [<dir> ...]'
  1394.  
  1395.     def get_option_parser(self):
  1396.         if not self._option_parser:
  1397.             p = OptionParser()
  1398.             p.add_option('-x', '--exclude',
  1399.                          help="skip files matching the regular expression",
  1400.                          default=None, action='store', dest='exclude')
  1401.             p.add_option('-V', '--version',
  1402.                          help="byte compile using this python version",
  1403.                          default='current', action='store', dest='version')
  1404.             self._option_parser = p
  1405.         return self._option_parser
  1406.  
  1407.     def check_args(self, global_options):
  1408.         if len(self.args) < 1:
  1409.             self._option_parser.print_help()
  1410.             sys.exit(1)
  1411.         self.pkgname = self.args[0]
  1412.         self.runtime = get_runtime_for_version(self.options.version)
  1413.         if not self.runtime:
  1414.             self.error("unknown runtime version %s" % self.options.version)
  1415.  
  1416.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1417.             self.error("package %s is not installed" % self.pkgname)
  1418.         self.pkg = DebPackage('package', self.pkgname, oldstyle=False,
  1419.                               default_runtime=self.runtime)
  1420.         self.pkg.read_version_info()
  1421.         return self.errors_occured
  1422.  
  1423.     def run(self, global_options):
  1424.         logging.debug('bccompile %s' % self.pkgname)
  1425.         runtimes = get_installed_runtimes()
  1426.         config = get_debian_config()
  1427.         bc_option = config.get('DEFAULT', 'byte-compile')
  1428.         requested = pyversions.requested_versions_for_runtime(self.pkg.version_field, version_only=True)
  1429.         used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  1430.         # called with directories as arguments
  1431.         if 0 and self.directories:
  1432.             try:
  1433.                 for version, dirs in self.pylib_dirs.items():
  1434.                     rt = get_runtime_for_version(version)
  1435.                     rt.byte_compile_dirs(dirs, bc_option, self.options.exclude)
  1436.                 if self.private_dirs:
  1437.                     version = self.pkg.version_field
  1438.                     if version == 'current':
  1439.                         version = pyversions.default_version(version_only=True)
  1440.                     rt = get_runtime_for_version(version)
  1441.                     rt.byte_compile_dirs(self.private_dirs, bc_option, self.options.exclude)
  1442.             except PyCentralError:
  1443.                 self.error("error byte-compiling package `%s'" % self.pkgname)
  1444.             return
  1445.  
  1446.         try:
  1447.             self.pkg.byte_compile(used_runtimes, bc_option, self.options.exclude)
  1448.         except PyCentralError:
  1449.             self.error("error byte-compiling package `%s'" % self.pkgname)
  1450.  
  1451. register_action(ActionByteCompile)
  1452.  
  1453. class ActionPkgInstall(Action):
  1454.     name = 'pkginstall'
  1455.     help = 'make a package available for all supported runtimes'
  1456.     usage = '[<options>] <package>'
  1457.  
  1458.     def get_option_parser(self):
  1459.         if not self._option_parser:
  1460.             p = OptionParser()
  1461.             p.add_option('-x', '--exclude',
  1462.                          help="skip files matching the regular expression",
  1463.                          default=None, action='store', dest='exclude')
  1464.             self._option_parser = p
  1465.         return self._option_parser
  1466.  
  1467.     def check_args(self, global_options):
  1468.         if len(self.args) != 1:
  1469.             self._option_parser.print_help()
  1470.             sys.exit(1)
  1471.         self.pkgname = self.args[0]
  1472.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1473.             self.error("package %s is not installed" % self.pkgname)
  1474.         return self.errors_occured
  1475.  
  1476.     def run(self, global_options):
  1477.         runtimes = get_installed_runtimes()
  1478.         config = get_debian_config()
  1479.         bc_option = config.get('DEFAULT', 'byte-compile')
  1480.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  1481.         pkg.read_version_info()
  1482.         requested = pyversions.requested_versions_for_runtime(pkg.version_field, version_only=True)
  1483.         used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  1484.         try:
  1485.             pkg.set_default_runtime_from_version_info()
  1486.         except ValueError:
  1487.             # Package doesn't provide support for any supported runtime
  1488.             if len(used_runtimes) == 0:
  1489.                 self.error('%s needs unavailable runtime (%s)'
  1490.                            % (self.pkgname, pkg.version_field))
  1491.             else:
  1492.                 # Still byte compile for the available runtimes (with the
  1493.                 # first matching runtime)
  1494.                 pkg.default_runtime = get_runtime_for_version(used_runtimes[0])
  1495.         logging.debug('\tavail=%s, pkg=%s, install=%s'
  1496.                       % ([rt.short_name for rt in runtimes],
  1497.                          pkg.version_field,
  1498.                          [rt.short_name for rt in used_runtimes]))
  1499.         try:
  1500.             pkg.install(used_runtimes, bc_option,
  1501.                         self.options.exclude, byte_compile_default=True)
  1502.         except PyCentralError, msg:
  1503.             self.error(msg)
  1504.  
  1505.         # cleanup after failed pkgprepare upgrades, see #552595.
  1506.         import subprocess
  1507.         p = subprocess.Popen(['dpkg-trigger',
  1508.                    '--no-await',
  1509.                    '--by-package=%s' % os.environ.get('DPKG_MAINTSCRIPT_PACKAGE', 'python'),
  1510.                    'cleanup-pkgprepare-updates'],
  1511.                   shell=False)
  1512.         sts = os.waitpid(p.pid, 0)[1]
  1513.         if sts != 0:
  1514.             self.error("error calling dpkg-trigger")
  1515.  
  1516. register_action(ActionPkgInstall)
  1517.  
  1518.  
  1519. class ActionPkgPrepare(Action):
  1520.     name = 'pkgprepare'
  1521.     help = 'prepare a package for all supported runtimes'
  1522.     usage = '[<options>] <package>'
  1523.  
  1524.     def get_option_parser(self):
  1525.         if not self._option_parser:
  1526.             p = OptionParser()
  1527.             p.add_option('-x', '--exclude',
  1528.                          help="skip files matching the regular expression",
  1529.                          default=None, action='store', dest='exclude')
  1530.             self._option_parser = p
  1531.         return self._option_parser
  1532.  
  1533.     def check_args(self, global_options):
  1534.         if len(self.args) != 1:
  1535.             self._option_parser.print_help()
  1536.             sys.exit(1)
  1537.         self.pkgname = self.args[0]
  1538.         # FIXME: run from the preinst, package may not exist
  1539.         #if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1540.         #    self.error("package %s is not installed" % self.pkgname)
  1541.         return self.errors_occured
  1542.  
  1543.     def run(self, global_options):
  1544.         # functionality disabled starting with 0.6.9
  1545.         return
  1546.  
  1547.         runtimes = get_installed_runtimes()
  1548.         config = get_debian_config()
  1549.         pkgconfig = PyCentralConfigParser()
  1550.         pkgconfig.optionxform = str
  1551.         pkgconfig.readfp(sys.stdin)
  1552.         version_field = pkgconfig.get('python-package', 'python-version')
  1553.         try:
  1554.             requested = pyversions.requested_versions_for_runtime(version_field, version_only=True)
  1555.         except pyversions.PyCentralEmptyValueError, msg:
  1556.             # cannot install yet; remove the symlinked files and byte code files from the old
  1557.             # version, rely on the pkginstall in the postinst.
  1558.             print "pycentral: required runtimes not yet installed, skip pkgprepare, call pkgremove"
  1559.             runtimes = get_installed_runtimes(with_unsupported=True)
  1560.             pkg = DebPackage('package', self.args[0], oldstyle=False)
  1561.             pkg.read_version_info()
  1562.             pkg.default_runtime = get_default_runtime()
  1563.             try:
  1564.                 pkg.remove(runtimes, remove_script_files=True)
  1565.             except PyCentralError, msg:
  1566.                 self.warn(msg)
  1567.             return
  1568.         used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  1569.         pkg = DebPackage('package', self.args[0], oldstyle=False, pkgconfig=pkgconfig)
  1570.         pkg.set_version_field(version_field)
  1571.         try:
  1572.             pkg.set_default_runtime_from_version_info()
  1573.         except ValueError:
  1574.             # Package doesn't provide support for any supported runtime
  1575.             if len(used_runtimes) == 0:
  1576.                 self.error('%s needs unavailable runtime (%s)'
  1577.                            % (self.pkgname, pkg.version_field))
  1578.             else:
  1579.                 # Still byte compile for the available runtimes (with the
  1580.                 # first matching runtime)
  1581.                 pkg.default_runtime = get_runtime_for_version(used_runtimes[0])
  1582.  
  1583.         if os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1584.             old_pkg = DebPackage('package', self.args[0], oldstyle=False)
  1585.             try:
  1586.                 old_pkg.read_version_info()
  1587.             except PyCentralError:
  1588.                 old_pkg.set_version_field(version_field)
  1589.  
  1590.             old_requested = pyversions.requested_versions_for_runtime(old_pkg.version_field, version_only=True)
  1591.             old_used_runtimes = [rt for rt in runtimes if rt.short_name in requested]
  1592.         else:
  1593.             old_pkg = None
  1594.             old_used_runtimes = []
  1595.         logging.debug('\tavail=%s, pkg=%s, prepare=%s'
  1596.                       % ([rt.short_name for rt in runtimes],
  1597.                          version_field,
  1598.                          [rt.short_name for rt in used_runtimes]))
  1599.         try:
  1600.             pkg.prepare(used_runtimes, old_used_runtimes, old_pkg)
  1601.         except PyCentralError, msg:
  1602.             self.error(msg)
  1603.  
  1604. register_action(ActionPkgPrepare)
  1605.  
  1606.  
  1607. class ActionBCRemove(Action):
  1608.     """remove the byte-compiled files in <package>.
  1609.  
  1610.     bccompile is a replacement for the current byte compilation
  1611.     generated by the dh_python debhelper script.
  1612.     """
  1613.     name = 'bcremove'
  1614.     help = 'remove the byte compiled .py files'
  1615.     usage = '<package>'
  1616.  
  1617.     def check_args(self, global_options):
  1618.         if len(self.args) != 1:
  1619.             self._option_parser.print_help()
  1620.             sys.exit(1)
  1621.         self.pkgname = self.args[0]
  1622.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1623.             self.error("package %s is not installed" % self.pkgname)
  1624.         return self.errors_occured
  1625.  
  1626.     def run(self, global_options):
  1627.         pkg = DebPackage('package', self.args[0], oldstyle=True)
  1628.         try:
  1629.             pkg.remove_bytecode()
  1630.         except PyCentralError, msg:
  1631.             self.error(msg)
  1632.  
  1633. register_action(ActionBCRemove)
  1634.  
  1635.  
  1636. class ActionPkgRemove(Action):
  1637.     """
  1638.     """
  1639.     name = 'pkgremove'
  1640.     help = 'remove a package installed for all supported runtimes'
  1641.     usage = '<package>'
  1642.  
  1643.     def check_args(self, global_options):
  1644.         if len(self.args) != 1:
  1645.             self._option_parser.print_help()
  1646.             sys.exit(1)
  1647.         self.pkgname = self.args[0]
  1648.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1649.             self.error("package %s is not installed" % self.pkgname)
  1650.         return self.errors_occured
  1651.  
  1652.     def run(self, global_options):
  1653.         runtimes = get_installed_runtimes(with_unsupported=True)
  1654.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  1655.         pkg.read_version_info(use_default_if_missing=True)
  1656.         try:
  1657.             pkg.set_default_runtime_from_version_info()
  1658.         except ValueError:
  1659.             # original runtime is already removed, use the default for removal
  1660.             pkg.default_runtime = get_default_runtime()
  1661.         try:
  1662.             pkg.remove(runtimes, remove_script_files=True)
  1663.         except PyCentralError, msg:
  1664.             self.error(msg)
  1665.  
  1666. register_action(ActionPkgRemove)
  1667.  
  1668. class ActionPkgList(Action):
  1669.     name = 'pkglist'
  1670.     help = 'list all pycentral-managed files for <package>'
  1671.     usage = '<package>'
  1672.  
  1673.     def check_args(self, global_options):
  1674.         if len(self.args) != 1:
  1675.             self._option_parser.print_help()
  1676.             sys.exit(1)
  1677.         self.pkgname = self.args[0]
  1678.         if not os.path.exists('/var/lib/dpkg/info/%s.list' % self.pkgname):
  1679.             self.error("package %s is not installed" % self.pkgname)
  1680.         return self.errors_occured
  1681.  
  1682.     def run(self, global_options):
  1683.         runtimes = get_installed_runtimes(with_unsupported=True)
  1684.         pkg = DebPackage('package', self.args[0], oldstyle=False)
  1685.         pkg.read_version_info()
  1686.         try:
  1687.             pkg.set_default_runtime_from_version_info()
  1688.         except ValueError:
  1689.             # original runtime may be removed, use the default
  1690.             pkg.default_runtime = get_default_runtime()
  1691.         try:
  1692.             for f in pkg.list(runtimes, list_script_files=True):
  1693.                 print f
  1694.         except PyCentralError, msg:
  1695.             self.error(msg)
  1696.  
  1697. register_action(ActionPkgList)
  1698.  
  1699.  
  1700. class ActionRuntimeInstall(Action):
  1701.     name = 'rtinstall'
  1702.     help = 'make installed packages available for this runtime'
  1703.  
  1704.     def get_option_parser(self):
  1705.         if not self._option_parser:
  1706.             p = OptionParser()
  1707.             p.add_option('-i', '--ignore-errors',
  1708.                          help="ignore errors during byte compilations",
  1709.                          default=None, action='store', dest='ignore')
  1710.             self._option_parser = p
  1711.         return self._option_parser
  1712.  
  1713.     def check_args(self, global_options):
  1714.         if len(self.args) != 1:
  1715.             self._option_parser.print_help()
  1716.             sys.exit(1)
  1717.         self.rtname = self.args[0]
  1718.         if self.rtname[-8:] == '-minimal':
  1719.             self.rtname = self.rtname[:-8]
  1720.         self.runtime = None
  1721.         for rt in get_installed_runtimes():
  1722.             if rt.name == self.rtname:
  1723.                 self.runtime = rt
  1724.                 break
  1725.         if not self.runtime:
  1726.             self.error('installed runtime %s not found' % self.rtname)
  1727.         self.rtname_short = self.rtname[6:]
  1728.         return self.errors_occured
  1729.  
  1730.     def run(self, global_options):
  1731.         packages = [(p, v) for p, v in read_dpkg_status()
  1732.                     if not p in (self.rtname, self.rtname+'-minimal')]
  1733.         needed_packages = []
  1734.         for pkgname, vstring in packages:
  1735.             try:
  1736.                 requested = list(pyversions.requested_versions(vstring, version_only=True))
  1737. #                if not self.rtname_short in requested:
  1738. #                    logging.info("%s not in what requested_versions() returned, adding it anyway" % self.rtname_short)
  1739. #                    requested.append(self.rtname_short)
  1740.  
  1741.             except ValueError:
  1742.                 logging.info('\tunsupported for %s: %s (%s)' % (self.rtname, pkgname, vstring))
  1743.                 continue
  1744.             if self.runtime.short_name in requested:
  1745.                 needed_packages.append((pkgname, vstring, requested))
  1746.         logging.info('\t%d packages with Python-Version info installed, %d for %s'
  1747.                      % (len(packages), len(needed_packages), self.rtname))
  1748.  
  1749.         # XXX not needed for an upgrade of a runtime
  1750.         byte_compile_for_default = (self.runtime == default_runtime)
  1751.  
  1752.         bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
  1753.         for pkgname, vstring, vinfo in needed_packages:
  1754.             try:
  1755.                 logging.info('\tsupport %s for %s' % (pkgname, self.rtname))
  1756.                 pkg = DebPackage('package', pkgname, oldstyle=False)
  1757.                 pkg.read_version_info()
  1758.                 try:
  1759.                     pkg.set_default_runtime_from_version_info()
  1760.                 except ValueError:
  1761.                     logging.warn('\t%s not available for %s (%s)'
  1762.                                  % (pkgname, self.rtname, pkg.version_field))
  1763.                 pkg.install([self.runtime], bc_option, None,
  1764.                             byte_compile_for_default,
  1765.                             ignore_errors = self.options.ignore != None)
  1766.             except PyCentralError, msg:
  1767.                 self.error('package %s: %s' % (pkgname, msg))
  1768.  
  1769. register_action(ActionRuntimeInstall)
  1770.  
  1771. class ActionRuntimeRemove(Action):
  1772.     name = 'rtremove'
  1773.     help = 'remove packages installed for this runtime'
  1774.  
  1775.     def check_args(self, global_options):
  1776.         if len(self.args) != 1:
  1777.             self._option_parser.print_help()
  1778.             sys.exit(1)
  1779.         self.rtname = self.args[0]
  1780.         if self.rtname[-8:] == '-minimal':
  1781.             self.rtname = self.rtname[:-8]
  1782.         self.runtime = None
  1783.         for rt in get_installed_runtimes(with_unsupported=True):
  1784.             if rt.name == self.rtname:
  1785.                 self.runtime = rt
  1786.                 break
  1787.         if not self.runtime:
  1788.             self.error('installed runtime %s not found' % self.rtname)
  1789.         return self.errors_occured
  1790.  
  1791.     def run(self, global_options):
  1792.         packages = [(p, v) for p, v in read_dpkg_status(verbose=True)
  1793.                     if not p in (self.rtname, self.rtname+'-minimal')]
  1794.         needed_packages = []
  1795.         import subprocess
  1796.         for pkgname, vstring in packages:
  1797.             if not os.path.exists('/var/lib/dpkg/info/%s.list' % pkgname):
  1798.                 # already removed, but /var/lib/dpkg/status not yet updated
  1799.                 continue
  1800.             cmd = ['/usr/bin/dpkg-query', '-W', '-f', '${Status}\n', pkgname]
  1801.             p = subprocess.Popen(cmd, bufsize=1,
  1802.                                  shell=False, stdout=subprocess.PIPE)
  1803.             fd = p.stdout
  1804.             status = fd.readline().strip().split()
  1805.             fd.close()
  1806.             if not 'installed' in status:
  1807.                 # already removed, but /var/lib/dpkg/status not yet updated
  1808.                 continue
  1809.             try:
  1810.                 requested = list(pyversions.requested_versions_for_runtime(vstring, version_only=True))
  1811.             except ValueError:
  1812.                 logging.info('\tunsupported for %s: %s (%s)' % (self.rtname, pkgname, vstring))
  1813.                 continue
  1814.             if self.runtime.short_name in requested:
  1815.                 needed_packages.append((pkgname, vstring, requested))
  1816.         logging.info('\t%d pycentral supported packages installed, %d for %s'
  1817.                      % (len(packages), len(needed_packages), self.rtname))
  1818.         failed = []
  1819.         for pkgname, vstring, vinfo in needed_packages:
  1820.             logging.info('\trtremove: remove package %s for %s' % (pkgname, self.rtname))
  1821.             pkg = DebPackage('package', pkgname)
  1822.             pkg.set_version_field(vstring)
  1823.             try:
  1824.                 pkg.set_default_runtime_from_version_info()
  1825.             except ValueError:
  1826.                 # original runtime is already removed, use the default for removal
  1827.                 pkg.default_runtime = get_default_runtime()
  1828.             try:
  1829.                 pkg.remove([self.runtime], remove_script_files=False)
  1830.             except PyCentralError, msg:
  1831.                 self.error('failed to remove %s support for package %s' % (self.rtname, pkgname), go_on=True)
  1832.                 failed.append(pkgname)
  1833.         if failed:
  1834.             self.error('failed to remove %s support for %d packages' % len(failed))
  1835.  
  1836. register_action(ActionRuntimeRemove)
  1837.  
  1838.  
  1839. class ActionUpdateDefault(Action):
  1840.     name = 'updatedefault'
  1841.     help = 'update the default python version'
  1842.     usage = '<old runtime> <new runtime>'
  1843.  
  1844.     def check_args(self, global_options):
  1845.         if len(self.args) != 2:
  1846.             self._option_parser.print_help()
  1847.             sys.exit(1)
  1848.         self.oldrtname = self.args[0]
  1849.         self.rtname = self.args[1]
  1850.         packages = read_dpkg_status()
  1851.         self.needed_packages = []
  1852.         for pkgname, vstring in packages:
  1853.             if vstring.find('current') == -1:
  1854.                 continue
  1855.             try:
  1856.                 versions = pyversions.requested_versions(vstring, version_only=True)
  1857.             except ValueError:
  1858.                 self.error("package %s is not ready to be updated for %s"
  1859.                            % (pkgname, self.rtname))
  1860.                 continue
  1861.             pkg = DebPackage('package', pkgname)
  1862.             self.needed_packages.append(pkg)
  1863.         return self.errors_occured
  1864.  
  1865.     def run(self, global_options):
  1866.         logging.info('\tupdate default: update %d packages for %s'
  1867.                      % (len(self.needed_packages), self.rtname))
  1868.         runtimes = get_installed_runtimes()
  1869.         default_rt = get_default_runtime()
  1870.         bc_option = get_debian_config().get('DEFAULT', 'byte-compile')
  1871.         try:
  1872.             for pkg in self.needed_packages:
  1873.                 pkg.read_version_info()
  1874.                 pkg.set_default_runtime_from_version_info()
  1875.                 if pkg.shared_files or pkg.private_files:
  1876.                     pkg.update_bytecode_files(runtimes, default_rt, bc_option)
  1877.         except PyCentralError, msg:
  1878.             self.error(msg)
  1879.  
  1880. register_action(ActionUpdateDefault)
  1881.  
  1882.  
  1883. class ActionCleanupPkgPrepareUpdates(Action):
  1884.     name = 'cleanup-pkgprepare-updates'
  1885.     help = 'cleanup dangling symlinks and byte code files'
  1886.  
  1887.     def check_args(self, global_options):
  1888.         if len(self.args) != 0:
  1889.             self._option_parser.print_help()
  1890.             sys.exit(1)
  1891.         return self.errors_occured
  1892.  
  1893.     def run(self, global_options):
  1894.         # needs to be done for 2.3, 2.4 and 2.5 (2.6 packages were not built
  1895.         # using python-central 0.6.8.
  1896.         dangling = self.check_dangling_links()
  1897.         if not dangling:
  1898.             return self.errors_occured
  1899.         self.warn("found %d dangling symlinks" % len(dangling))
  1900.         # check if these belong to packages
  1901.         self.warn("checking for links owned by packages (this may take some time)")
  1902.         packaged = self.links_in_packages(dangling)
  1903.         unowned = [link for link in dangling if not link in packaged]
  1904.         
  1905.         # then remove those not belonging to any package
  1906.         dirs = []
  1907.         removed = 0
  1908.         for fn in unowned:
  1909.             try:
  1910.                 os.unlink(fn + 'c')
  1911.                 os.unlink(fn + 'o')
  1912.             except OSError, e:
  1913.                 pass
  1914.             try:
  1915.                 os.unlink(fn)
  1916.                 removed += 1
  1917.                 if not os.path.dirname(fn) in dirs:
  1918.                     dirs.append(os.path.dirname(fn))
  1919.             except OSError, e:
  1920.                 self.error("error removing dangling symlink: %s" % str(e),
  1921.                            go_on=True)
  1922.         for d in dirs:
  1923.             try:
  1924.                 os.rmdir(d)
  1925.             except OSError, e:
  1926.                 pass
  1927.  
  1928.         if len(packaged) > 0:
  1929.             for fn in packaged:
  1930.                 self.warn('dangling symlink owned by package: %s' % fn)
  1931.         if removed:
  1932.             self.warn('removed %d dangling symlinks not owned by any package' % removed)
  1933.         return self.errors_occured
  1934.  
  1935.     def check_dangling_links(self):
  1936.         dangling = []
  1937.         for pv in ('2.3', '2.4', '2.5'):
  1938.             sdir = '/usr/lib/python%s/site-packages' % pv
  1939.             if not os.path.isdir(sdir):
  1940.                 continue
  1941.             for root, dirs, files in os.walk(sdir, topdown=False):
  1942.                 # remember files
  1943.                 # rember dirs with removed files and try to remove these
  1944.                 for name in files:
  1945.                     link = os.path.join(root, name)
  1946.                     if not os.path.islink(link):
  1947.                         continue
  1948.                     target = os.readlink(link)
  1949.                     if target.startswith('..'):
  1950.                         target = os.path.normpath(target)
  1951.                     if os.path.exists(target):
  1952.                         continue
  1953.                     if not target.startswith('/usr/share/pyshared/'):
  1954.                         continue
  1955.                     dangling.append(link)
  1956.         return dangling
  1957.  
  1958.     def locate(self, arg_list, cmd_list):
  1959.         from subprocess import PIPE, Popen
  1960.         (stdout, stderr) = Popen(cmd_list + arg_list,
  1961.                                  env={"LANG" : "C"},
  1962.                                  shell=False,
  1963.                                  stdout=PIPE, stderr=PIPE).communicate()
  1964.         if stdout:
  1965.             for line in map(string.strip, stdout.split("\n")):
  1966.                 if line == '': continue
  1967.                 fn = line.split(':', 1)[1][1:]
  1968.                 if fn in arg_list:
  1969.                     self.packaged.add(fn)
  1970.         if stderr:
  1971.             for line in map(string.strip, stderr.split("\n")):
  1972.                 if line == '': continue
  1973.                 fn = line.split(':', 1)[1][1:-11]
  1974.                 self.notfound += 1
  1975.  
  1976.     def links_in_packages(self, links):
  1977.         def chunks(l):
  1978.             if 'SC_ARG_MAX' in os.sysconf_names:
  1979.                 chunk_max = os.sysconf('SC_ARG_MAX')
  1980.             else:
  1981.                 chunk_max = 16384
  1982.             chunk_max -= reduce(lambda x,y: x+y,
  1983.                                 [len(k)+len(v)+3 for k,v in os.environ.items()])
  1984.             chunk_max -= 20
  1985.             chunk = []
  1986.             lchunk = 0
  1987.             all_chunks = []
  1988.             for i in l:
  1989.                 if lchunk + len(i)+1 > chunk_max:
  1990.                     all_chunks.append(chunk)
  1991.                     chunk = []
  1992.                     lchunk = 0
  1993.                 chunk.append(i)
  1994.                 lchunk += len(i) + 1
  1995.             if chunk:
  1996.                 all_chunks.append(chunk)
  1997.             return all_chunks
  1998.  
  1999.         self.packaged = set()
  2000.         self.notfound = 0
  2001.         all_chunks = chunks(links)
  2002.         arg_list = all_chunks[0]
  2003.         cmd_list = ["dpkg","-S"]
  2004.         self.locate(arg_list, cmd_list)
  2005.         for arg_list in all_chunks[1:]:
  2006.             self.locate(arg_list, cmd_list)
  2007.         return self.packaged
  2008.  
  2009. register_action(ActionCleanupPkgPrepareUpdates)
  2010.  
  2011. class ActionList(Action):
  2012.     name = 'list'
  2013.     help = 'List all pycentral-managed files'
  2014.  
  2015.     def check_args(self, global_options):
  2016.         if len(self.args) != 0:
  2017.             self._option_parser.print_help()
  2018.             sys.exit(1)
  2019.         return self.errors_occured
  2020.  
  2021.     def run(self, global_options):
  2022.         runtimes = get_installed_runtimes(with_unsupported=True)
  2023.         for (p, v) in read_dpkg_status():
  2024.             pkg = DebPackage('package', p)
  2025.             pkg.read_version_info()
  2026.             for f in pkg.list(runtimes, list_script_files=True):
  2027.                 print f
  2028.  
  2029. register_action(ActionList)
  2030.  
  2031.  
  2032. class ActionShowDefault(Action):
  2033.     name = 'showdefault'
  2034.     help = 'Show default python version number'
  2035.  
  2036.     def check_args(self, global_options):
  2037.         if len(self.args) != 0:
  2038.             self._option_parser.print_help()
  2039.             sys.exit(1)
  2040.         return self.errors_occured
  2041.  
  2042.     def run(self, global_options):
  2043.         print pyversions.default_version(version_only=True)
  2044.         sys.stderr.write("pycentral showdefault is deprecated, use `pyversions -vd'\n")
  2045.  
  2046. register_action(ActionShowDefault)
  2047.  
  2048.  
  2049. class ActionShowVersions(Action):
  2050.     name = 'showversions'
  2051.     help = 'Show version numbers of supported python versions'
  2052.  
  2053.     def check_args(self, global_options):
  2054.         if len(self.args) != 0:
  2055.             self._option_parser.print_help()
  2056.             sys.exit(1)
  2057.  
  2058.         return self.errors_occured
  2059.  
  2060.     def run(self, global_options):
  2061.         supported = pyversions.supported_versions()
  2062.         versions = [d[6:] for d in supported if re.match(r'python\d\.\d', d)]
  2063.         print ' '.join(versions)
  2064.         sys.stderr.write("pycentral showversions is deprecated, use `pyversions -vs'\n")
  2065.  
  2066. register_action(ActionShowVersions)
  2067.  
  2068. class ActionShowSupported(Action):
  2069.     name = 'showsupported'
  2070.     help = 'Show the supported python versions'
  2071.  
  2072.     def check_args(self, global_options):
  2073.         if len(self.args) != 0:
  2074.             self._option_parser.print_help()
  2075.             sys.exit(1)
  2076.         return self.errors_occured
  2077.  
  2078.     def run(self, global_options):
  2079.         supported = pyversions.supported_versions()
  2080.         print ' '.join(supported)
  2081.         sys.stderr.write("pycentral showsupported is deprecated, use `pyversions -s'\n")
  2082.  
  2083. register_action(ActionShowSupported)
  2084.  
  2085.  
  2086. class ActionPyCentralDir(Action):
  2087.     name = 'pycentraldir'
  2088.     help = 'Show the pycentral installation directory for the package'
  2089.     usage = '<package>'
  2090.  
  2091.     def check_args(self, global_options):
  2092.         if len(self.args) != 1:
  2093.             self._option_parser.print_help()
  2094.             sys.exit(1)
  2095.         self.pkgname = self.args[0]
  2096.         return self.errors_occured
  2097.  
  2098.     def run(self, global_options):
  2099.         if shared_base2[-1] == '/':
  2100.             print shared_base2[:-1]
  2101.         else:
  2102.             print shared_base2
  2103.  
  2104. register_action(ActionPyCentralDir)
  2105.  
  2106.  
  2107. class ActionVersion(Action):
  2108.     name = 'version'
  2109.     help = 'Show the pycentral version'
  2110.  
  2111.     def check_args(self, global_options):
  2112.         if len(self.args) != 0:
  2113.             self._option_parser.print_help()
  2114.             sys.exit(1)
  2115.  
  2116.         return self.errors_occured
  2117.  
  2118.     def run(self, global_options):
  2119.         sys.stdout.write("%s\n" % pycentral_version)
  2120.  
  2121. register_action(ActionVersion)
  2122.  
  2123.  
  2124. class ActionDebhelper(Action):
  2125.     name = 'debhelper'
  2126.     help = 'move files to pycentral location, variable substitutions'
  2127.     usage = '[-p|--provides] [--no-move|--include-links] <package> [<package directory>]'
  2128.  
  2129.     def get_option_parser(self):
  2130.         if not self._option_parser:
  2131.             envvar = os.environ.get('DH_PYCENTRAL', '')
  2132.             substvars_default = 'no'
  2133.             if 'substvars=file' in envvar:
  2134.                 substvars_default = 'file'
  2135.             if 'substvars=stdout' in envvar:
  2136.                 substvars_default = 'stdout'
  2137.  
  2138.             p = OptionParser()
  2139.             p.add_option('-p', '--provides',
  2140.                          help="generate substitution for python:Provides",
  2141.                          default='add-provides' in envvar, action='store_true', dest='provides')
  2142.             p.add_option('--no-move', '--nomove',
  2143.                          help="do not move files to pycentral location",
  2144.                          default='no-move' in envvar or 'nomove' in envvar, action='store_true', dest='nomove')
  2145.             p.add_option('--include-links',
  2146.                          help="include links to the pycentral location",
  2147.                          default='include-links' in envvar, action='store_true', dest='includelinks')
  2148.             p.add_option('--stdout',
  2149.                          help="just print substitution variables to stdout",
  2150.                          default='stdout' in envvar, action='store_true', dest='stdout')
  2151.             p.add_option('--substvars',
  2152.                          help="where to print substitution vars (no, file, stdout)",
  2153.                          default=substvars_default, dest='substvars')
  2154.             p.add_option('--no-act', '--dry-run',
  2155.                          help="dry run",
  2156.                          default=('dry-run' in envvar) or ('no-act' in envvar),
  2157.                          action='store_true', dest='dryrun')
  2158.             self._option_parser = p
  2159.         return self._option_parser
  2160.  
  2161.     def check_args(self, global_options):
  2162.         if not len(self.args) in (1, 2):
  2163.             self._option_parser.print_help()
  2164.             sys.exit(1)
  2165.         if 'file' in self.options.substvars:
  2166.             self.options.substvars = 'file'
  2167.         if 'stdout' in self.options.substvars:
  2168.             self.options.substvars = 'stdout'
  2169.         if self.options.nomove and self.options.includelinks:
  2170.             self.error("options `--no-move' and `--include-links' conflict")
  2171.         return self.errors_occured
  2172.  
  2173.     def run(self, global_options):
  2174.         if len(self.args) < 2:
  2175.             pkgdir = 'debian/' + self.args[0]
  2176.         else:
  2177.             pkgdir = self.args[1]
  2178.         try:
  2179.             pkg = DebPackage('package', self.args[0], pkgdir=pkgdir,
  2180.                              parse_versions=self.options.substvars!='no')
  2181.             if not self.options.nomove:
  2182.                 pkg.move_files()
  2183.             if self.options.includelinks:
  2184.                 pkg.add_shared_links()
  2185.             pkg.read_pyfiles()
  2186.             if self.options.substvars!='no':
  2187.                 pkg.gen_substvars()
  2188.         except PyCentralVersionMissingError, msg:
  2189.             self.warn(msg)
  2190.             return
  2191.         except PyCentralError, msg:
  2192.             self.error(msg)
  2193.  
  2194.         out = None
  2195.         if self.options.stdout or self.options.substvars == 'stdout':
  2196.             out = sys.stdout
  2197.         elif self.options.substvars == 'file':
  2198.             out = file('debian/%s.substvars' % pkg.name, 'a+')
  2199.         if out:
  2200.             out.write('python:Versions=%s\n' % pkg.version_field)
  2201.             out.write('python:Depends=%s\n' % pkg.depends)
  2202.             out.write('python:Provides=%s\n' % pkg.provides)
  2203.  
  2204.  
  2205. register_action(ActionDebhelper)
  2206.  
  2207. # match a string with the list of available actions
  2208. def action_matches(action, actions):
  2209.     prog = re.compile('[^-]*?-'.join(action.split('-')))
  2210.     return [a for a in actions if prog.match(a)]
  2211.  
  2212. def usage(stream, msg=None):
  2213.     print >>stream, msg
  2214.     print >>stream, "use `%s --help' for help on actions and arguments" % program
  2215.     print >>stream
  2216.     sys.exit(1)
  2217.  
  2218. # parse command line arguments
  2219. def parse_options(args):
  2220.     shortusage = 'usage: %s [<option> ...] <action> <pkgname>' % program
  2221.     parser = OptionParser(usage=shortusage)
  2222.     parser.disable_interspersed_args()
  2223.  
  2224.     # setup the parsers object
  2225.     parser.remove_option('-h')
  2226.     parser.add_option('-h', '--help',
  2227.                       help='help screen',
  2228.                       action='store_true', dest='help')
  2229.     parser.add_option('-v', '--verbose',
  2230.                       help='verbose mode',
  2231.                       action='store_true', dest='verbose')
  2232.  
  2233.     global_options, args = parser.parse_args()
  2234.     # Print the help screen and exit
  2235.     if len(args) == 0 or global_options.help:
  2236.         parser.print_help()
  2237.         print "\nactions:"
  2238.         action_names = known_actions.keys()
  2239.         action_names.sort()
  2240.         for n in action_names:
  2241.             print "  %-21s %s" % (n, known_actions[n].help)
  2242.         print ""
  2243.         sys.exit(1)
  2244.  
  2245.     # check if the specified action really exists
  2246.     action_name = args[0]
  2247.     del args[0]
  2248.     matching_actions = action_matches(action_name, known_actions.keys())
  2249.     if len(matching_actions) == 0:
  2250.         usage(sys.stderr, "unknown action `%s'" % action_name)
  2251.     elif len(matching_actions) > 1:
  2252.         usage(sys.stderr,
  2253.               "ambiguous action `%s', matching actions: %s"
  2254.               % (action_name, str(list(matching_actions))))
  2255.     else:
  2256.         action_name = matching_actions[0]
  2257.  
  2258.     # instantiate an object for the action and parse the remaining arguments
  2259.     action = known_actions[action_name]()
  2260.     action_options, action_names = action.parse_args(args)
  2261.  
  2262.     return global_options, action
  2263.  
  2264. class Logging:
  2265.     DEBUG, INFO, WARN, ERROR = range(4)
  2266.     def __init__(self, level=WARN):
  2267.         self.fd = None
  2268.         self.level = level
  2269.         try:
  2270.             self.fd = file('/var/log/pycentral.log', 'a+')
  2271.         except IOError:
  2272.             self.fd = None
  2273.     def msg(self, level, s):
  2274.         if level < self.level:
  2275.             return
  2276.         d = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  2277.         if self.fd:
  2278.             self.fd.write('%s %s %s\n' % (d, level, s))
  2279.             self.fd.flush()
  2280.         sys.stdout.write('pycentral: %s\n' % (s))
  2281.         sys.stdout.flush()
  2282.     def info(self, s):
  2283.         self.msg(self.INFO, s)
  2284.     def warn(self, s):
  2285.         self.msg(self.WARN, s)
  2286.     def error(self, s):
  2287.         self.msg(self.ERROR, s)
  2288.         sys.stderr.write('%s\n' % s)
  2289.     def debug(self, s):
  2290.         self.msg(self.DEBUG, s)
  2291.  
  2292.     def debug_list(self, tab, s, l, n=4):
  2293.         l2 = l[:min(n, len(l))]
  2294.         if len(l) > n:
  2295.             l2.append('...')
  2296.         self.msg(self.DEBUG, "%s%s (%s/%s)" % (tab, s, len(l2), len(l)))
  2297.         if len(l2) > 0:
  2298.             logging.debug('%s    %s' % (tab, l2))
  2299.  
  2300. def setup_logging(loglevel=Logging.WARN, verbose=False):
  2301.     levels = ['debug', 'info', 'warn', 'error']
  2302.     env_level = os.environ.get('PYCENTRAL', 'warn').lower()
  2303.     for i in range(len(levels)):
  2304.         if env_level.find(levels[i]) != -1:
  2305.             loglevel = i
  2306.     if verbose:
  2307.         loglevel = Logging.DEBUG
  2308.     global logging
  2309.     logging = Logging(loglevel)
  2310.  
  2311. def main():
  2312.     global_options, action = parse_options(sys.argv[1:])
  2313.  
  2314.     os.umask(0022)
  2315.  
  2316.     # setup logging stuff
  2317.     setup_logging(Logging.WARN, global_options.verbose)
  2318.     if action.name == 'debhelper' or action.name.startswith('show'):
  2319.         pass
  2320.     else:
  2321.         logging.debug('pycentral ' + ' '.join(sys.argv[1:]))
  2322.  
  2323.     # check the arguments according to the action called
  2324.     if action.check_args(global_options):
  2325.         sys.exit(1)
  2326.  
  2327.     # run the action and exit
  2328.     rv = action.run(global_options)
  2329.     sys.exit(rv)
  2330.  
  2331.  
  2332. # call the main routine
  2333. if __name__ == '__main__':
  2334.     main()
  2335.